﻿using System.Collections;
using System.Net.DnsToolkit.Util;

namespace System.Net.DnsToolkit.Packet.Lazy {
    public class Header : IHeader {

        private const int OpCodeMask = 0x78;       // 01 11 10 00
        private const int ResponseCodeMask = 0x0F; // 00 00 11 11

        private readonly IBufferReader _buffer;

        public Header(IBufferReader buffer) {
            _buffer = buffer;
        }

        #region Id

        private bool _idLoaded;
        private readonly object _idLock = new object();
        private const int IdOffset = 0;

        private void LoadId() {

            if (!_idLoaded) {
                lock (_idLock) {
                    if (!_idLoaded) {
                        _id = _buffer.Read16(IdOffset);
                        _idLoaded = true;
                    }
                }
            }

        }

        ushort _id;
        public ushort Id {
            get {
                LoadId();
                return _id;
            }
            set {
                lock (_idLock) {
                    _idLoaded = true;
                    _id = value;
                }
            }
        }

        #endregion

        #region QOATR : QueryType, OpCode, AuthoritativeAnswer, Truncation, RecursionDesired

        private bool _qoatrLoaded;
        private readonly object _qoatrLock = new object();
        private const int QoatrOffset = 2;

        private void LoadQoatr() {
            
            if (!_qoatrLoaded) {
                lock (_qoatrLock) {
                    if (!_qoatrLoaded) {

                        byte bqoatr = _buffer.Read8(QoatrOffset);
                        var ba = new BitArray(new[] { bqoatr });

                        _queryType = Enum<QREnum>.ToObject(ba[7] ? 1 : 0);
                        _opCode = Enum<OpCodeEnum>.ToObject((bqoatr & OpCodeMask) >> 3);

                        _authoritativeAnswer = ba[2];
                        _truncation = ba[1];
                        _recursionDesired = ba[0];

                        _qoatrLoaded = true;

                    }
                }
            }

        }

        QREnum _queryType;
        public QREnum QueryType {
            get {
                LoadQoatr();
                return _queryType;
            }
            set {
                lock (_qoatrLock) {
                    _queryType = value;
                }
            }
        }

        OpCodeEnum _opCode;
        public OpCodeEnum OpCode {
            get {
                LoadQoatr();
                return _opCode;
            }
            set {
                lock (_qoatrLock) {
                    _opCode = value;
                }
            }
        }

        bool _authoritativeAnswer;
        public bool AuthoritativeAnswer {
            get {
                LoadQoatr();
                return _authoritativeAnswer;
            }
            set {
                lock (_qoatrLock) {
                    _authoritativeAnswer = value;
                }
            }
        }

        bool _truncation;
        public bool Truncation {
            get {
                LoadQoatr();
                return _truncation;
            }
            set {
                lock (_qoatrLock) {
                    _truncation = value;
                }
            }
        }

        bool _recursionDesired;
        public bool RecursionDesired {
            get {
                LoadQoatr();
                return _recursionDesired;
            }
            set {
                lock (_qoatrLock) {
                    _recursionDesired = value;
                }
            }
        }

        #endregion

        #region RRANR: RecursionAvailable, Reserved, AnswerAuthenticated, NotAuthenticatedAcceptable, ResponseCode

        private bool _rranrLoaded;
        private readonly object _rranrLock = new object();
        private const int RranrOffset = 3;

        private void LoadRranr() {
            if (!_rranrLoaded) {
                lock (_rranrLock) {
                    if (!_rranrLoaded) {


                        byte brranr = _buffer.Read8(RranrOffset);
                        var bb = new BitArray(new[] { brranr });

                        _recursionAvailable = bb[7];
                        _reserved = bb[6];
                        _answerAuthenticated = bb[5];
                        _notAuthenticatedAcceptable = bb[4];

                        _responseCode = Enum<RCodeEnum>.ToObject(brranr & ResponseCodeMask);

                        _rranrLoaded = true;
                    }
                }
            }
        }


        bool _recursionAvailable;
        public bool RecursionAvailable {
            get {
                LoadRranr();
                return _recursionAvailable;
            }
            set {
                lock (_rranrLock) {
                    _recursionAvailable = value;
                    _rranrLoaded = true;
                }
            }
        }

        bool _reserved;
        public bool Reserved {
            get {
                LoadRranr();
                return _reserved;
            }
            set {
                lock (_rranrLock) {
                    _reserved = value;
                    _rranrLoaded = true;
                }
            }
        }

        bool _answerAuthenticated;
        public bool AnswerAuthenticated {
            get {
                LoadRranr();
                return _answerAuthenticated;
            }
            set {
                lock (_rranrLock) {
                    _answerAuthenticated = value;
                    _rranrLoaded = true;
                }
            }
        }

        bool _notAuthenticatedAcceptable;
        public bool NotAuthenticatedAcceptable {
            get {
                LoadRranr();
                return _notAuthenticatedAcceptable;
            }
            set {
                lock (_rranrLock) {
                    _notAuthenticatedAcceptable = value;
                    _rranrLoaded = true;
                }
            }
        }

        RCodeEnum _responseCode;
        public RCodeEnum ResponseCode {
            get {
                LoadRranr();
                return _responseCode;
            }
            set {
                lock (_rranrLock) {
                    _responseCode = value;
                    _rranrLoaded = true;
                }
            }
        }
        #endregion

        #region QuestionCount
        private bool _qcountLoaded;
        private readonly object _qcountLock = new object();
        private const int QcountOffset = 4;

        private void LoadQuestionCount() {
            if (!_qcountLoaded) {
                lock (_qcountLock) {
                    if (!_qcountLoaded) {
                        _questionCount = _buffer.Read16(QcountOffset);
                        _qcountLoaded = true;
                    }
                }
            }
        }
        ushort _questionCount;
        public ushort QuestionCount {
            get {
                LoadQuestionCount();
                return _questionCount;
            }
            set {
                lock (_qcountLock) {
                    _questionCount = value;
                    _qcountLoaded = true;
                }
            }
        }
        #endregion

        #region AnswerCount
        private bool _ancountLoaded;
        private readonly object _ancountLock = new object();
        private const int AncountOffset = 6;

        private void LoadAnswerCount() {
            if (!_ancountLoaded) {
                lock (_ancountLock) {
                    if (!_ancountLoaded) {
                        _answerCount = _buffer.Read16(AncountOffset);
                        _ancountLoaded = true;
                    }
                }
            }
        }
        ushort _answerCount;
        public ushort AnswerCount {
            get {
                LoadAnswerCount();
                return _answerCount;
            }
            set {
                lock (_ancountLock) {
                    _answerCount = value;
                    _ancountLoaded = true;
                }
            }
        }
        #endregion

        #region AuthorityCount
        private bool _aucountLoaded;
        private readonly object _aucountLock = new object();
        private const int AucountOffset = 8;

        private void LoadAuthorityCount() {
            if (!_aucountLoaded) {
                lock (_aucountLock) {
                    if (!_aucountLoaded) {
                        _authorityCount = _buffer.Read16(AucountOffset);
                        _aucountLoaded = true;
                    }
                }
            }
        }
        ushort _authorityCount;
        public ushort AuthorityCount {
            get {
                LoadAuthorityCount();
                return _authorityCount;
            }
            set {
                lock (_ancountLock) {
                    _authorityCount = value;
                    _aucountLoaded = true;
                }
            }
        }
        #endregion

        #region ResourceCount
        private bool _rrcountLoaded;
        private readonly object _rrcountLock = new object();
        private const int RrcountOffset = 10;

        private void LoadResourceCount() {
            if (!_rrcountLoaded) {
                lock (_rrcountLock) {
                    if (!_rrcountLoaded) {
                        _resourceCount = _buffer.Read16(RrcountOffset);
                        _rrcountLoaded = true;
                    }
                }
            }
        }
        ushort _resourceCount;
        public ushort ResourceCount {
            get {
                LoadResourceCount();
                return _resourceCount;
            }
            set {
                lock (_ancountLock) {
                    _resourceCount = value;
                    _rrcountLoaded = true;
                }
            }
        }
        #endregion

    }
}
